import random
import re
import json
import requests
import os
import browsercookie
import sys
import traceback

try:
    import cookielib
except ImportError:
    import http.cookiejar as cookielib


class BilibiliDownloader(object):
    def __init__(self, ):
        """
        """
        self.ps = 30
        self.tid = 0
        self.pn = 1
        self.header = {
            'Range': 'bytes=0-',
            'referer': "",
            'origin': 'https://www.bilibili.com/',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 Edg/85.0.564.63'
        }
        print("开始加载Chrome浏览器cookies,请确保浏览器已经关闭")
        self.__load_cookie(browsercookie.chrome())
        self.count = 0
        self.total = 0
        self.download_path = os.path.dirname(__file__)

    def main(self):
        print(f"""
        请输入数字选择功能:
              1: 设置下载文件保存路径(当前路径:{self.download_path})
              2: 下载当前用户收藏视频/音频
              3：下载指定用户 发布视频/音频
              4：下载指定视频/音频
              5: 重新读取COOKIE信息
              0：退出程序  
        """)
        choices = [
            {
                "id": 1,
                "title": f"设置下载文件保存路径(当前路径:{self.download_path})",
                "func": self._set_download_path,
                "data": None,
            },
            {
                "id": 2,
                "title": "下载当前用户收藏视频/音频",
                "func": self._download_my_favlist,
                "data": None,
            },
            {
                "id": 3,
                "title": "下载指定用户发布的视频/音频",
                "func": self._download_user,
                "data": None,
            },
            {
                "id": 4,
                "title": "下载指定视频/音频",
                "func": self._download_url,
                "data": None,
            },
            {
                "id": 5,
                "title": "重新读取COOKIE信息",
                "func": self._reload_cookie,
                "data": None,
            },
            {
                "id": 0,
                "title": "退出程序",
                "func": sys.exit,
                "data": None,
            },
        ]
        self.do_choices(choices=choices, prompt1="请输入数字选择功能:", prompt2="请输入功能ID：", end=False)

    def do_choices(self, choices, prompt1="请选择操作", prompt2="请选择操作", end=True):
        """
        choices = []choice
        choice = {"id":xx,"title":"xxx","func":"xx",data:"xx"}
        :return:
        """
        print(prompt1)
        for choice in choices:
            print(f"""\t\t{choice["id"]}: {choice["title"]}""")
        select = None
        while select is None:
            try:
                choice_id = int(input(prompt2))
                for choice in choices:
                    if choice["id"] == choice_id:
                        select = choice
            except:
                pass
        if choice["func"] is None:
            return choice["data"]
        try:
            if choice["data"] is None:
                choice["func"]()
            else:
                choice["func"](choice["data"])
        except:
            print(f"error:{sys.exc_info()}\ntraceback:{traceback.format_exc()}")
            input("按任意键继续")
        if end:
            # 结束则返回
            return
        else:
            # 否则循环
            return self.do_choices(choices, prompt1, prompt2, end)

    def _set_download_path(self):
        download_path = input("请输入下载文件保存路径")
        if os.path.exists(download_path):
            self.download_path = download_path
            print(f"下载文件保存路径设置成功,当前保存路径为 {self.download_path}")
        else:
            print(f"下载文件保存路径设置失败,路径{download_path}不存在")

    def _reload_cookie(self):
        choices = [
            {
                "id": 1,
                "title": "加载Chrome浏览器COOKIE,请先关闭浏览器",
                "data": browsercookie.chrome(),
                "func": self.__load_cookie,
            }, {
                "id": 2,
                "title": "加载Edge浏览器COOKIE,请先关闭浏览器",
                "data": browsercookie.edge(),
                "func": self.__load_cookie,
            }, {
                "id": 3,
                "title": "加载FireFox浏览器COOKIE,请先关闭浏览器",
                "data": browsercookie.firefox(),
                "func": self.__load_cookie,
            }, {
                "id": 4,
                "title": "加载safari浏览器COOKIE,请先关闭浏览器",
                "data": browsercookie.safari(),
                "func": self.__load_cookie,
            }, {
                "id": 5,
                "title": "加载所有浏览器COOKIE,请先关闭浏览器",
                "data": browsercookie.load(),
                "func": self.__load_cookie,
            }, {
                "id": 6,
                "title": "返回 主菜单",
                "data": None,
                "func": self.main,
            }, {
                "id": 0,
                "title": "退出程序",
                "data": None,
                "func": sys.exit,
            }
        ]
        self.do_choices(choices=choices)

    def __load_cookie(self, all_cookies):
        uid = None
        cookie_jar = cookielib.CookieJar()
        for cookie in all_cookies:
            if cookie.domain == ".bilibili.com":
                cookie_jar.set_cookie(cookie)
                if cookie.name == "DedeUserID":
                    uid = cookie.value
                    print(f"获取到用户UID={self.uid}")

        if uid is None:
            print(f"未获取到用户UID")
            return
        self.uid = uid
        self.cookies = cookie_jar
        resp = requests.get(url="https://api.bilibili.com/x/space/v2/myinfo", cookies=self.cookies).json()
        print(f"""
        用户信息:
              用户名 :  {resp["data"]["profile"]["name"]}
              UID   :  {resp["data"]["profile"]["mid"]}
              VIP   :  {resp["data"]["profile"]["vip"]["label"]["text"]}
        """)

    def BL_get_all(self, has_choose=False):
        url = f"https://api.bilibili.com/x/space/arc/search?mid={self.uid}&ps={self.ps}&tid={self.tid}&pn={self.pn}&keyword=&order=pubdate&jsonp=jsonp"
        self.header['referer'] = url
        result = requests.get(url, headers=self.header, cookies=self.cookies).json()
        if not has_choose:
            print(f"result:{result}")
            print("此用户共有以下视频分类:")
            for item in result["data"]["list"]["tlist"].values():
                print(f"{item}")
                print(f"{item['tid']}:{item['name']},视频数量{item['count']}个")
            print(f"0:所有视频,共{result['data']['page']['count']}个")
            choice = input("请输入选择：")
            if choice == '0':
                print("你选择了视频分类：全部视频")
                self.total = result['data']['page']['count']
                self.download_all(result)
            else:
                print(f"你选择了视频分类:{result['data']['list']['tlist'][choice]['name']}")
                self.tid = choice
                self.BL_get_all(True)
        else:
            self.total = result['data']['page']['count']
            self.download_all(result)

    def download_all(self, response_json_data):
        for item in response_json_data["data"]["list"]["vlist"]:
            self.count += 1
            print(f"总共需要下载{self.total}个视频,正在下载第{self.count}个视频:{item['title']}")
            url = f"https://www.bilibili.com/video/{item['bvid']}"
            self.BL_download(url, item["title"])
        if len(response_json_data["data"]["list"]["vlist"]) == self.ps:
            self.pn += 1
            self.BL_get_all(True)

    def __get_video_download_url(self, vid):
        url = f"https://www.bilibili.com/video/{vid}"
        html = requests.get(url, headers=self.header, cookies=self.cookies).text
        json_data = re.findall('window.__playinfo__=(.*?)</script>', html)[0]
        video_url = json.loads(json_data)['data']['dash']['video'][0]['baseUrl']
        audio_url = json.loads(json_data)['data']['dash']['audio'][0]['baseUrl']
        return video_url, audio_url

    def __download_video_mp3(self, item):
        video_url, audio_url = self.__get_video_download_url(item['bvid'])
        os.makedirs(os.path.join(self.download_path, item['bvid']))
        self.__download_file__(audio_url, os.path.join(self.download_path, item['bvid'], item['title'] + '.mp3'))
        self.__set_mp3_metadata(item)

    def __set_mp3_metadata(self, item):
        """
        设置MP3文件元数据
        :param item:
        :return:
        """
        try:
            import mutagen
            filename = os.path.join(self.download_path, item['bvid'], item['title'] + '.mp3')
            audio = muagen.File(filename)
            print(audio.tags)
        except:
            pass

    def __download_video_mp4(self, item):
        video_url, audio_url = self.__get_video_download_url(item['bvid'])
        os.makedirs(os.path.join(self.download_path, item['bvid']))
        self.__download_file__(audio_url, os.path.join(self.download_path, item['bvid'], item['title'] + '.mp3'))
        self.__download_file__(video_url, os.path.join(self.download_path, item['bvid'], item['title'] + '.flv'))
        self.__merge_video(item)
    def __merge_video(self,item):
        """
        合并音频和视频,合并后原文件删除
        需要按照ffmpeg 地址 http://ffmpeg.org/download.html
        :param item:
        :return: bool，成功返回True,失败返回False
        """
        rc=0
        try:
            import ffmpeg
            flv_file = os.path.join(self.download_path, item['bvid'], item['title'] + '.flv')
            mp3_file = os.path.join(self.download_path, item['bvid'], item['title'] + '.mp3')
            output_file = os.path.join(self.download_path, item['bvid'], item['title'] + '.mp4')
            audio = ffmpeg.input(flv_file)
            video = ffmpeg.input(mp3_file)
            out = ffmpeg.output(video,audio，output_file)
            out.run()
            os.remove(flv_file)
            os.remove(mp3_file)
        except:
            rc=1
        return rc==0



    def BL_download(self, url, title):
        self.header['referer'] = url
        html = requests.get(url, headers=self.header, cookies=self.cookies).text
        json_data = re.findall('window.__playinfo__=(.*?)</script>', html)[0]
        # video_name = re.findall(',"title":"(.*?)","', html)[0]
        # if video_name == '':
        #     video_name = int(random.random() * 2 * 1000)
        # if len(str(video_name)) > 20:
        #   video_name = video_name[:20]
        video_dir = os.path.join(path, self.uid, title)
        os.makedirs(video_dir)
        video = json.loads(json_data)['data']['dash']['video'][0]['baseUrl']
        self.__download_file__(video, os.path.join(video_dir, title + '.flv'))
        print("【BiLiBiLi】: {} 视频下载完成！".format(title))
        audio = json.loads(json_data)['data']['dash']['audio'][0]['baseUrl']
        self.__download_file__(audio, os.path.join(video_dir, title + '.mp3'))
        print("【BiLiBiLi】: {} 音频下载完成！".format(title))

    def __download_file__(self, url, rel_path):
        r = requests.get(url, headers=self.header)
        with open(rel_path, 'wb')as f:
            f.write(r.content)

    def _download_my_favlist(self):
        if self.uid is None:
            print("未从COOKIE中读取到当前用户UID,请在浏览器上登录后关闭浏览器再尝试")
            return
        resp = requests.get(url=f"https://api.bilibili.com/x/v3/fav/folder/created/list-all?up_mid={self.uid}",
                            cookies=self.cookies).json()
        if resp["code"] != 0:
            print(f"获取用户收藏夹失败:%s" % resp["message"])
            return
        count = resp["data"]["count"]
        fav_list = resp["data"]["list"]
        if count == 0:
            print(f"获取用户收藏夹成功,但是用户没有收藏夹")
            return
        self.__list_my_favlist(fav_list)

    def __list_fav_all_media(self, fav):
        """
        列出收藏夹视频
        :param fav:
        :return:
        """
        url = f"""https://api.bilibili.com/x/v3/fav/resource/list?media_id={fav["id"]}""" + \
              f"""&pn=1&ps={fav["media_count"]}&keyword=&order=mtime&type=0&tid=0&platform=web"""
        resp = requests.get(url=f"”“",
                            cookies=self.cookies).json()

    def __list_my_favlist(self, fav_list):
        count = len(fav_list)
        prompt1 = f"获取用户收藏夹成功,共有{count}个收藏夹,具体如下:"
        fav_choices = []
        for i in range(len(fav_list)):
            fav = fav_list[i]
            fav_choices.append({
                "id": i,
                "title": f"%s(包含%d个视频)" % (fav["title"], fav["media_count"]),
                "data": fav,
                "func": None,
            })
        num = len(fav_list) - 1
        fav_choice = self.do_choices(choices=fav_choices, prompt1=prompt1,
                                     prompt2="请输入收藏夹名称前的序号(0-%d):" % num)
        print(f"你选择了收藏夹(%s)，共包含%d 个视频" % (fav_choice["title"], fav_choice["media_count"]))
        choices = [
            {
                "id": 1,
                "title": "下载此收藏夹所有视频",
                "data": fav_choice,
                "func": self.__download_fav_all_mp4,
            },
            {
                "id": 2,
                "title": "下载此收藏夹所有音频",
                "data": fav_choice,
                "func": self.__download_fav_all_mp3,
            },
            {
                "id": 3,
                "title": "列出此收藏夹下面的视频",
                "data": fav_choice,
                "func": self.__list_fav_all_media,
            },
            {
                "id": 4,
                "title": "返回 收藏夹选择",
                "data": fav_list,
                "func": self.__list_my_favlist,
            },
            {
                "id": 5,
                "title": "返回 主菜单",
                "data": None,
                "func": self.main,
            },
            {
                "id": 0,
                "title": "退出程序",
                "data": None,
                "func": sys.exit,
            }
        ]
        self.do_choices(choices=choices)

    def __download_fav_all_mp4(self, fav):
        pass

    def __download_fav_all_mp3(self, fav):
        pass


if __name__ == '__main__':
    BilibiliDownloader().main()
